﻿using SharpDepend;
using SoundEngine3.AudioProcessing.Algorithms;
using SoundEngine3.MIDI.Meta;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.HighLevel
{
    /// <summary>
    /// http://en.wikiaudio.org/ADSR_envelope
    /// </summary>
    [ModuleDisplay("Env ADSR")]
    public class EnvelopeModule : BaseModule
    {
        private EnvelopeGenerator mADSREnvelop = new EnvelopeGenerator();
        private double mLastValue;
        private int mTotalPressedNotes;
        private double mAttackRate;
        private double mReleaseRate;
        private double mDecayRate;

        public readonly ModuleInput InputGate;
        public readonly ModuleOutput Output;
        public readonly ModuleOutput OutOnRunAndIdle;
        public readonly InputModuleParameter ParameterAttack;
        public readonly InputModuleParameter ParameterDecay;
        public readonly InputModuleParameter ParameterSustain;
        public readonly InputModuleParameter ParameterRelease;

        public EnvelopeModule(ModuleManager manager)
            : base(manager)
        {
            AddInput(InputGate = new ModuleInput("On", IOType.Midi, this));
            AddOutput(Output = new ModuleOutput("Out", "Output", IOType.Audio, this));
            AddOutput(OutOnRunAndIdle = new ModuleOutput("Rai", "OnRunAndIdle", IOType.Midi, this));

            ParameterAttack = new InputModuleParameter(this, "Att", mADSREnvelop.AttackRate, IOType.Audio, InputModuleType.OnlyDirect);
            ParameterAttack.Label = "Attack";
            ParameterAttack.MinValue = 0;
            ParameterAttack.MaxValue = 44100;
            ParameterAttack.DirectGet += delegate { return mAttackRate; };
            ParameterAttack.DirectSet += delegate (double value) { mADSREnvelop.AttackRate = (mAttackRate = value); };
            AddModuleParameter(ParameterAttack);

            ParameterRelease = new InputModuleParameter(this, "Rel", mADSREnvelop.ReleaseRate, IOType.Audio, InputModuleType.OnlyDirect);
            ParameterRelease.Label = "Release";
            ParameterRelease.MinValue = 0;
            ParameterRelease.MaxValue = 44100;
            ParameterRelease.DirectGet += delegate { return mReleaseRate; };
            ParameterRelease.DirectSet += delegate (double value) { mADSREnvelop.ReleaseRate = (mReleaseRate = value); };
            AddModuleParameter(ParameterRelease);

            ParameterSustain = new InputModuleParameter(this, "Sus", mADSREnvelop.SustainLevel, IOType.Audio, InputModuleType.OnlyDirect);
            ParameterSustain.Label = "SustainLevel";
            ParameterSustain.MinValue = 0;
            ParameterSustain.MaxValue = 1;
            ParameterSustain.DirectGet += delegate { return mADSREnvelop.SustainLevel; };
            ParameterSustain.DirectSet += delegate (double value) { mADSREnvelop.SustainLevel = value; };
            AddModuleParameter(ParameterSustain);

            ParameterDecay = new InputModuleParameter(this, "Dec", mADSREnvelop.DecayRate, IOType.Audio, InputModuleType.OnlyDirect);
            ParameterDecay.Label = "Decay";
            ParameterDecay.MinValue = 0;
            ParameterDecay.MaxValue = 44100;
            ParameterDecay.DirectGet += delegate { return mDecayRate; };
            ParameterDecay.DirectSet += delegate (double value) { mADSREnvelop.DecayRate = (mDecayRate = value); };
            AddModuleParameter(ParameterDecay);

            // TODO: Smaple rate pitch verkar fungera. Bara att envelope ger ut exempel frequency 1000, som blir annat när vi har ändrat sample rate.
            // Jag tror vi behöver lägga till en SampleRateAjuster-module som justerar frekvenser till rätt.
        }

        //private double AdjustSampleRate { get { return SoundEngineContext.SampleRatePitch(44100); } }

        public override void ChangeSampleRate(double sampleRate)
        {
            //mADSREnvelop.AttackRate = ParameterAttack.RawValue * AdjustSampleRate;
            //mADSREnvelop.ReleaseRate = ParameterRelease.RawValue * AdjustSampleRate;
            //mADSREnvelop.DecayRate = ParameterDecay.RawValue * AdjustSampleRate;
        }

        public override void Process()
        {
            if (InputGate.IsConnected && Output.IsConnected)
            {
                int length = Output.AudioBuffer.Length;
                for (int i = 0; i < length; i++)
                {
                    var state = InputGate.GetMidiState(i);

                    if (state != 0)
                    {
                        if (state == SoundEngine3.MIDI.Meta.MidiStatus.NOTE_ON)
                        {
                            if (mTotalPressedNotes == 0)
                            {
                                mADSREnvelop.Gate(true);
                            }
                            ++mTotalPressedNotes;
                        }
                        else if (state == SoundEngine3.MIDI.Meta.MidiStatus.NOTE_OFF)
                        {
                            --mTotalPressedNotes;

                            if (mTotalPressedNotes < 0)
                            {
                                Log.Assert(mTotalPressedNotes >= 0, "Got more note off than note on.");
                                mTotalPressedNotes = 0;
                            }

                            if (mTotalPressedNotes == 0)
                            {
                                mADSREnvelop.Gate(false);
                            }
                        }
                    }

                    double value = mADSREnvelop.Process();
                    Output.AudioBuffer[i] = value;

                    if (mLastValue != value)
                    {
                        if (value == 0.0)
                        {
                            var noteOff = MidiMessage.NoteOff;
                            noteOff.DeltaFrame = i;
                            OutOnRunAndIdle.AddMidiMessage(noteOff);
                        }
                        else if(mLastValue == 0.0)
                        {
                            var noteOn = MidiMessage.NoteOn;
                            noteOn.DeltaFrame = i;
                            OutOnRunAndIdle.AddMidiMessage(noteOn);
                        }
                    }

                    mLastValue = value;
                }
            }
        }
    }
}
